package transport

import (
	"addsvc/internal/endpoint"
	"addsvc/internal/pb"
	"addsvc/internal/utils/method"
	"context"

	transportGRPC "github.com/go-kit/kit/transport/grpc"
	"github.com/go-kit/log"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
	"google.golang.org/grpc/metadata"
)

type grpcServer struct {
	sum    transportGRPC.Handler
	concat transportGRPC.Handler
	pb.UnimplementedServiceServer
}

func (s *grpcServer) Sum(ctx context.Context, req *pb.SumRequest) (*pb.SumResponse, error) {
	_, resp, err := s.sum.ServeGRPC(ctx, req)
	if err != nil {
		return nil, err
	}
	return resp.(*pb.SumResponse), nil
}

func (s *grpcServer) Concat(ctx context.Context, req *pb.ConcatRequest) (*pb.ConcatResponse, error) {
	_, rep, err := s.concat.ServeGRPC(ctx, req)
	if err != nil {
		return nil, err
	}
	return rep.(*pb.ConcatResponse), nil
}

func NewGRPCServer(endpoints *endpoint.Endpoints, logger log.Logger, tracer trace.Tracer) pb.ServiceServer {
	options := []transportGRPC.ServerOption{
		transportGRPC.ServerErrorLogger(logger),
		transportGRPC.ServerBefore(grpcToContext()),
	}
	sum := makeEndpoint(endpoints.SumPoint, method.Sum, logger, tracer)
	sumServer := transportGRPC.NewServer(
		sum,
		decodeGRPCSumServerRequest,
		encodeGRPCSumServerResponse,
		options...,
	)

	concat := makeEndpoint(endpoints.ConcatPoint, method.Concat, logger, tracer)
	concatServer := transportGRPC.NewServer(
		concat,
		decodeGRPCConcatServerRequest,
		encodeGRPCConcatServerResponse,
		options...,
	)
	return &grpcServer{
		sum:    sumServer,
		concat: concatServer,
	}
}

func grpcToContext() transportGRPC.ServerRequestFunc {
	return func(ctx context.Context, md metadata.MD) context.Context {
		carrier := propagation.MapCarrier{}
		for k, v := range md {
			carrier[k] = v[0]
		}
		propagator := otel.GetTextMapPropagator()

		return propagator.Extract(ctx, carrier)
	}
}

// decodeGRPCSumServerRequest is decode a GRPC request to the endpoint.SumResponse struct.
// Primarily useful in a server.
func decodeGRPCSumServerRequest(_ context.Context, grpcReq any) (any, error) {
	req := grpcReq.(*pb.SumRequest)
	return endpoint.SumRequest{A: int(req.A), B: int(req.B)}, nil
}

// encodeGRPCSumServerResponse is encode a endpoint.SumResponse struct to the GRPC request.
// Primarily useful in a server.
func encodeGRPCSumServerResponse(_ context.Context, grpcResp any) (any, error) {
	resp := grpcResp.(endpoint.SumResponse)
	err := ""
	if resp.Err != nil {
		err = resp.Err.Error()
	}
	return &pb.SumResponse{Sum: int64(resp.V), Err: err}, nil
}

// decodeGRPCSumServerRequest is decodes a GRPC request to the endpoint.ConcatRequest struct.
// Primarily useful in a server.
func decodeGRPCConcatServerRequest(_ context.Context, grpcReq any) (any, error) {
	req := grpcReq.(*pb.ConcatRequest)
	return endpoint.ConcatRequest{A: req.A, B: req.B}, nil
}

// encodeGRPCConcatServerResponse is encode a endpoint.ConcatResponse struct to the GRPC request.
// Primarily useful in a server.
func encodeGRPCConcatServerResponse(_ context.Context, grpcResp any) (any, error) {
	resp := grpcResp.(endpoint.ConcatResponse)
	err := ""
	if resp.Err != nil {
		err = resp.Err.Error()
	}
	return &pb.ConcatResponse{V: resp.V, Err: err}, nil
}
